#ifndef P2P_SESSION_HPP
#define P2P_SESSION_HPP

#include "Common.hpp"
#include "../muduo/net/TcpConnection.h"
#include "../muduo/net/EventLoop.h"
#include "../muduo/net/TimerId.h"
#include "../muduo/base/Timestamp.h"
#include "../wire/serialize.hpp"

#include "flowrate/flowrate.hpp"
#include <spdlog/spdlog.h>
#include <mutex>
#include <memory>
#include <functional>
typedef std::vector<unsigned char> bytes;
typedef std::function<void(unsigned char chID,const bytes& msgBytes)> receiveCbFunc;

class Peer;
class ReputationManager;
class Host;
class Capability;

const static int kMaxPacketMsgPayloadSizeDefault = 1024;
const static int kMaxPacketMsgOverheadSize       = 14;

const static int kNumBatchPacketMsgs = 10;
const static int kMinReadBufferSize  = 1024;
const static int kMinWriteBufferSize = 65536;
const static int64_t kUpdateStats        = 2;        //seconds
const static int64_t kDefaultFlushThrottle = 100;
const static int kDefaultSendQueueCapacity   = 1;
const static int kDefaultRecvBufferCapacity  = 4096;
const static int kDefaultRecvMessageCapacity = 22020096;     // 21MB
const static int64_t kDefaultSendRate            = 512000; // 500KB/s
const static int64_t kDefaultRecvRate            = 512000; // 500KB/s
const static int64_t kDefaultSendTimeout         = 10;
const static int64_t kDefaultPingInterval        = 60;
const static int64_t kDefaultPongTimeout         = 45;

struct SessionConfig
{
    int64_t sendRate;
    int64_t recvRate;

    int maxPacketMsgPayloadSize;
    int64_t flushThrottle;
    int64_t pingInterval;
    int64_t pongTimeout;

    SessionConfig():sendRate(kDefaultSendRate),
                    recvRate(kDefaultRecvRate),
                    maxPacketMsgPayloadSize(kMaxPacketMsgPayloadSizeDefault),
                    flushThrottle(kDefaultFlushThrottle),
                    pingInterval(kDefaultPingInterval),
                pongTimeout(kDefaultPongTimeout) {}

    SessionConfig(int64_t sendRate,int64_t recvRate,int maxPacketMsgPayloadSize,
    int64_t flushThrottle,int64_t pingInterval,int64_t pongTimeout):
            sendRate(sendRate),
            recvRate(recvRate),
            maxPacketMsgPayloadSize(maxPacketMsgPayloadSize),
            flushThrottle(flushThrottle),
            pingInterval(pingInterval),
            pongTimeout(pongTimeout) {}
};

class Session: public std::enable_shared_from_this<Session>
{
public:
    Session(Host* ower,muduo::net::EventLoop* loop, muduo::net::TcpConnectionPtr& conn, std::shared_ptr<Peer>const & peer,
                std::shared_ptr<SessionConfig> sessionConfig = std::make_shared<SessionConfig>(),
                std::shared_ptr<spdlog::logger> logger = spdlog::stdout_color_mt("Session"));
    virtual ~Session();

    std::string toString();

    void disconnect(DisconnectReason const reason);

    bool isConnected() const { return conn_->connected(); }

    muduo::Timestamp connectionTime() { return connect_; }
    
    std::shared_ptr<Peer> peer() const{ return peer_.lock(); }

    muduo::Timestamp lastReceived() const { return lastReceived_; }

    // ReputationManager& repMan();

    void send(unsigned char chID,const bytes& message);


private:

    void onMessage(const muduo::net::TcpConnectionPtr& conn,
                 muduo::net::Buffer* buf,
                 muduo::Timestamp);
    void ping();
    Host* ower_;
    muduo::net::EventLoop * loop_;
    muduo::net::TcpConnectionPtr conn_;
    
    std::shared_ptr<flowrate::Monitor> sendMonitor_;
    std::shared_ptr<flowrate::Monitor> recvMonitor_;
    
    std::shared_ptr<SessionConfig> config_;

    std::weak_ptr<Peer> peer_;    ///< The Peer object.
    bool dropped_;          ///< If true, we've already divested ourselves of this peer. We're just waiting for the reads & writes to fail before the shared_ptr goes OOS and the destructor kicks in.
 
    mutable std::mutex mutexOfInfo_;

    muduo::Timestamp connect_;               /// Time point of connection
    muduo::Timestamp lastPing_;              /// Time point of last ping
    muduo::Timestamp lastReceived_;          /// Time point of lastReceived
    
    muduo::net::TimerId pingTimeout_;
    muduo::net::TimerId pongTimeout_;

    std::shared_ptr<spdlog::logger> logger_;
    // std::map<CapDesc, std::shared_ptr<Capability> > capabilities_;  ///< The peer's capability set.
};

struct packetMsg
{
    unsigned char chID;
    bytes data;
    packetMsg(const unsigned char _chID,const bytes& _data):
        chID(_chID),data(_data){}
        
    ADD_SERIALIZE_METHODS;
    template <typename Stream, typename Operation>
    inline void SerializationOp(Stream& s, Operation ser_action) {
        READWRITE(chID);
        READWRITE(data);
    }

    size_t size()
    {
        return (data.size()+1)*sizeof(unsigned char);
    }
};

// template<class PeerCap>
// std::shared_ptr<PeerCap> capabilityFromSession(SessionFace const& session, uint256 const& version=PeerCap::version())
// {
//     try 
// 	{ 
// 		return std::static_pointer_cast<PeerCap>(session.capabilities().at(std::make_pair(PeerCap::name(), version)));
// 	}
// 	catch (...)
// 	{
// 		return nullptr;
// 	}
// }

#endif // !P2P_SESSION_HPP

